package br.com.ppj.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;

import br.com.ppj.util.XMLErrorHandler;


/**
 * Classe utilitaria que converte XML em Objetos e vice-versa
 * 
 * @author Hidalgo
 *
 * @param <T>
 */
public class UtilXML<T>{
	
	T to;
	JAXBContext jaxbContext;
	
	public UtilXML(T objeto) throws JAXBException {
		this.to = objeto;
		jaxbContext = JAXBContext.newInstance(to.getClass());
	}
	
	/**
	 * Converte um arquivo XML em Objeto, sem validação
	 * 
	 * @param file
	 * @return O objeto do tipo T genérico
	 * @throws Exception 
	 */
	public T XmlToObjeto(File file) throws Exception{
		return XmlToObjeto(file, null);
	}
	
	/**
	 * Converte uma String no formato XML em Objeto, sem validação
	 * @param xml
	 * @return O objeto do tipo T genérico
	 * @throws Exception 
	 */
	public T XmlToObjeto(String xml) throws Exception{
		return XmlToObjeto(xml, null);
	}
	
	/**
	 * Converte uma String no formato XML em Objeto.
	 * 
	 * @param xml string no formato XML
	 * @param xsd Nome do arquivo xsd, para a validação do xml. Se o valor for nulo
	 * ignora-se a validação
	 * @return O objeto do tipo T genérico ou null se falhou na validação
	 * @throws Exception 
	 */
	@SuppressWarnings("unchecked")
	public T XmlToObjeto(String xml, String xsd) throws Exception{
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();   
        
        if (xsd != null) {        	
			validaXML(xml, xsd);			
        } 
        return (T) unmarshaller.unmarshal(new StreamSource(new StringReader(xml)));
    }
	
	/**
	 * Converte um Arquivo XML em objeto.
	 * 
	 * @param file
	 * @param xsd Nome do arquivo xsd, para a validação do xml. Se o valor for nulo
	 * ignora-se a validação
	 * @return O objeto do tipo T genérico  ou null se falhou na validação
	 * @throws Exception 
	 */
	@SuppressWarnings("unchecked")
	public T XmlToObjeto(File file, String xsd) throws Exception{
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        
        if (xsd != null) {
        	validaXML(file, xsd);		
        }        
        
        return (T) unmarshaller.unmarshal(file);
    }
	
	/**
	 * Converte o Objeto em XML
	 * 
	 * @return String no formato XML
	 * 
	 * @throws JAXBException
	 * @throws SAXException
	 * @throws IOException
	 */
	public String ObjetoToXml() throws JAXBException, SAXException, IOException {
		Marshaller marshaller = jaxbContext.createMarshaller();
		StringWriter writer = new StringWriter();
		marshaller.marshal(to, writer);		
		
		return writer.toString();
    }
	
	/**
	 * Valida um xml a partir de um schema
	 * 
	 * @param xml
	 * @param schemaXSD
	 * @throws Exception
	 */
	private static void validaXML(File xml, String schemaXSD) throws Exception {
		SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.XML_NS_URI);
		File xsd = IoTools.createFile(schemaXSD);
		System.out.println("Schema: " + xsd.getAbsolutePath());
	    Schema schema = factory.newSchema(xsd);   
	    Validator validator = schema.newValidator();

	    XMLErrorHandler handler = new XMLErrorHandler();
		validator.setErrorHandler(handler);
		
		FileInputStream in = new FileInputStream(xml);
		InputStreamReader inputStreamReader = new InputStreamReader(in, "UTF-8"); 
	    
		StreamSource source = new StreamSource(inputStreamReader);
		validator.validate(source);
		if (handler.getCount() > 0) {
			String message = handler.getMessage();
			throw new Exception(message);
		}
	}
	
	
	/**
	 * Valida um xml a partir de um schema
	 * 
	 * @param xml
	 * @param schemaXSD
	 * @throws Exception
	 */
	private static void validaXML(String xml, String schemaXSD) throws Exception {
		SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.XML_NS_URI);
		File xsd = IoTools.createFile(schemaXSD);
		
	    Schema schema = factory.newSchema(xsd);   
	    Validator validator = schema.newValidator();

	    XMLErrorHandler handler = new XMLErrorHandler();
		validator.setErrorHandler(handler);
	
		validator.validate(new StreamSource(new StringReader(xml)));
		if (handler.getCount() > 0) {
			String message = handler.getMessage();
			throw new Exception(message);
		}
		
	}
	
}

